{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "nbpresent": {
     "id": "42b5e80b-ad1d-4335-a1f7-10a91127e3dc"
    }
   },
   "source": [
    "# Breast Cancer Prediction \n",
    "_**Predict Breast Cancer using SageMaker's Linear-Learner with features derived from images of Breast Mass**_\n",
    "\n",
    "---\n",
    "\n",
    "---\n",
    "\n",
    "## Contents\n",
    "\n",
    "1. [Background](#Background)\n",
    "1. [Setup](#Setup)\n",
    "1. [Data](#Data)\n",
    "1. [Train](#Train)\n",
    "1. [Host](#Host)\n",
    "1. [Predict](#Predict)\n",
    "1. [Extensions](#Extensions)\n",
    "\n",
    "---\n",
    "\n",
    "## Background\n",
    "This notebook illustrates how one can use SageMaker's algorithms for solving applications which require `linear models` for prediction. For this illustration, we have taken an example for breast cancer prediction using UCI'S breast cancer diagnostic data set available at https://archive.ics.uci.edu/ml/datasets/Breast+Cancer+Wisconsin+%28Diagnostic%29. The data set is also available on Kaggle at https://www.kaggle.com/uciml/breast-cancer-wisconsin-data. The purpose here is to use this data set to build a predictve model of whether a breast mass image indicates benign or malignant tumor. The data set will be used to illustrate\n",
    "\n",
    "* Basic setup for using SageMaker.\n",
    "* converting datasets to protobuf format used by the Amazon SageMaker algorithms and uploading to S3. \n",
    "* Training SageMaker's linear learner on the data set.\n",
    "* Hosting the trained model.\n",
    "* Scoring using the trained model.\n",
    "\n",
    "\n",
    "\n",
    "---\n",
    "\n",
    "## Setup\n",
    "\n",
    "Let's start by specifying:\n",
    "\n",
    "* The SageMaker role arn used to give learning and hosting access to your data. The snippet below will use the same role used by your SageMaker notebook instance, if you're using other.  Otherwise, specify the full ARN of a role with the SageMakerFullAccess policy attached.\n",
    "* The S3 bucket that you want to use for training and storing model objects."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "isConfigCell": true,
    "nbpresent": {
     "id": "6427e831-8f89-45c0-b150-0b134397d79a"
    }
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import boto3\n",
    "import re\n",
    "from sagemaker import get_execution_role\n",
    "\n",
    "role = get_execution_role()\n",
    "\n",
    "bucket = '<your_s3_bucket_name_here>'# enter your s3 bucket where you will copy data and model artifacts\n",
    "prefix = 'sagemaker/DEMO-breast-cancer-prediction' # place to upload training files within the bucket"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbpresent": {
     "id": "b2548d66-6f8f-426f-9cda-7a3cd1459abd"
    }
   },
   "source": [
    "Now we'll import the Python libraries we'll need."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "nbpresent": {
     "id": "bb88eea9-27f3-4e47-9133-663911ea09a9"
    }
   },
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import io\n",
    "import time\n",
    "import json\n",
    "import sagemaker.amazon.common as smac"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbpresent": {
     "id": "142777ae-c072-448e-b941-72bc75735d01"
    }
   },
   "source": [
    "---\n",
    "## Data\n",
    "\n",
    "Data Source: https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data\n",
    "        https://www.kaggle.com/uciml/breast-cancer-wisconsin-data\n",
    "\n",
    "Let's download the data and save it in the local folder with the name data.csv and take a look at it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "nbpresent": {
     "id": "f8976dad-6897-4c7e-8c95-ae2f53070ef5"
    }
   },
   "outputs": [],
   "source": [
    "data = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data', header = None)\n",
    "\n",
    "# specify columns extracted from wbdc.names\n",
    "data.columns = [\"id\",\"diagnosis\",\"radius_mean\",\"texture_mean\",\"perimeter_mean\",\"area_mean\",\"smoothness_mean\",\n",
    "                \"compactness_mean\",\"concavity_mean\",\"concave points_mean\",\"symmetry_mean\",\"fractal_dimension_mean\",\n",
    "                \"radius_se\",\"texture_se\",\"perimeter_se\",\"area_se\",\"smoothness_se\",\"compactness_se\",\"concavity_se\",\n",
    "                \"concave points_se\",\"symmetry_se\",\"fractal_dimension_se\",\"radius_worst\",\"texture_worst\",\n",
    "                \"perimeter_worst\",\"area_worst\",\"smoothness_worst\",\"compactness_worst\",\"concavity_worst\",\n",
    "                \"concave points_worst\",\"symmetry_worst\",\"fractal_dimension_worst\"] \n",
    "\n",
    "# save the data\n",
    "data.to_csv(\"data.csv\", sep=',', index=False)\n",
    "\n",
    "# print the shape of the data file\n",
    "print(data.shape)\n",
    "\n",
    "# show the top few rows\n",
    "display(data.head())\n",
    "\n",
    "# describe the data object\n",
    "display(data.describe())\n",
    "\n",
    "# we will also summarize the categorical field diganosis \n",
    "display(data.diagnosis.value_counts())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Key observations:\n",
    "* Data has 569 observations and 32 columns.\n",
    "* First field is 'id'.\n",
    "* Second field, 'diagnosis', is an indicator of the actual diagnosis ('M' = Malignant; 'B' = Benign).\n",
    "* There are 30 other numeric features available for prediction."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Create Features and Labels\n",
    "#### Split the data into 80% training, 10% validation and 10% testing."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "rand_split = np.random.rand(len(data))\n",
    "train_list = rand_split < 0.8\n",
    "val_list = (rand_split >= 0.8) & (rand_split < 0.9)\n",
    "test_list = rand_split >= 0.9\n",
    "\n",
    "data_train = data[train_list]\n",
    "data_val = data[val_list]\n",
    "data_test = data[test_list]\n",
    "\n",
    "train_y = ((data_train.iloc[:,1] == 'M') +0).as_matrix();\n",
    "train_X = data_train.iloc[:,2:].as_matrix();\n",
    "\n",
    "val_y = ((data_val.iloc[:,1] == 'M') +0).as_matrix();\n",
    "val_X = data_val.iloc[:,2:].as_matrix();\n",
    "\n",
    "test_y = ((data_test.iloc[:,1] == 'M') +0).as_matrix();\n",
    "test_X = data_test.iloc[:,2:].as_matrix();"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbpresent": {
     "id": "ff9d10f9-b611-423b-80da-6dcdafd1c8b9"
    }
   },
   "source": [
    "Now, we'll convert the datasets to the recordIO-wrapped protobuf format used by the Amazon SageMaker algorithms, and then upload this data to S3.  We'll start with training data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "nbpresent": {
     "id": "cd8e3431-79d9-40b6-91d1-d67cd61894e7"
    }
   },
   "outputs": [],
   "source": [
    "train_file = 'linear_train.data'\n",
    "\n",
    "f = io.BytesIO()\n",
    "smac.write_numpy_to_dense_tensor(f, train_X.astype('float32'), train_y.astype('float32'))\n",
    "f.seek(0)\n",
    "\n",
    "boto3.Session().resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train', train_file)).upload_fileobj(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbpresent": {
     "id": "71cbcebd-a2a5-419e-8e50-b2bc0909f564"
    }
   },
   "source": [
    "Next we'll convert and upload the validation dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "nbpresent": {
     "id": "bd113b8e-adc1-4091-a26f-a426149fe604"
    }
   },
   "outputs": [],
   "source": [
    "validation_file = 'linear_validation.data'\n",
    "\n",
    "f = io.BytesIO()\n",
    "smac.write_numpy_to_dense_tensor(f, val_X.astype('float32'), val_y.astype('float32'))\n",
    "f.seek(0)\n",
    "\n",
    "boto3.Session().resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'validation', validation_file)).upload_fileobj(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbpresent": {
     "id": "f3b125ad-a2d5-464c-8cfa-bd203034eee4"
    }
   },
   "source": [
    "---\n",
    "## Train\n",
    "\n",
    "Now we can begin to specify our linear model.  Amazon SageMaker's Linear Learner actually fits many models in parallel, each with slightly different hyperparameters, and then returns the one with the best fit.  This functionality is automatically enabled.  We can influence this using parameters like:\n",
    "\n",
    "- `num_models` to increase to total number of models run.  The specified parameters will always be one of those models, but the algorithm also chooses models with nearby parameter values in order to find a solution nearby that may be more optimal.  In this case, we're going to use the max of 32.\n",
    "- `loss` which controls how we penalize mistakes in our model estimates.  For this case, let's use absolute loss as we haven't spent much time cleaning the data, and absolute loss will be less sensitive to outliers.\n",
    "- `wd` or `l1` which control regularization.  Regularization can prevent model overfitting by preventing our estimates from becoming too finely tuned to the training data, which can actually hurt generalizability.  In this case, we'll leave these parameters as their default \"auto\" though."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Specify container images used for training and hosting SageMaker's linear-learner"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# See 'Algorithms Provided by Amazon SageMaker: Common Parameters' in the SageMaker documentation for an explanation of these values.\n",
    "from sagemaker.amazon.amazon_estimator import get_image_uri\n",
    "container = get_image_uri(boto3.Session().region_name, 'linear-learner')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "nbpresent": {
     "id": "397fb60a-c48b-453f-88ea-4d832b70c919"
    }
   },
   "outputs": [],
   "source": [
    "linear_job = 'DEMO-linear-' + time.strftime(\"%Y-%m-%d-%H-%M-%S\", time.gmtime())\n",
    "\n",
    "\n",
    "\n",
    "print(\"Job name is:\", linear_job)\n",
    "\n",
    "linear_training_params = {\n",
    "    \"RoleArn\": role,\n",
    "    \"TrainingJobName\": linear_job,\n",
    "    \"AlgorithmSpecification\": {\n",
    "        \"TrainingImage\": container,\n",
    "        \"TrainingInputMode\": \"File\"\n",
    "    },\n",
    "    \"ResourceConfig\": {\n",
    "        \"InstanceCount\": 1,\n",
    "        \"InstanceType\": \"ml.c4.2xlarge\",\n",
    "        \"VolumeSizeInGB\": 10\n",
    "    },\n",
    "    \"InputDataConfig\": [\n",
    "        {\n",
    "            \"ChannelName\": \"train\",\n",
    "            \"DataSource\": {\n",
    "                \"S3DataSource\": {\n",
    "                    \"S3DataType\": \"S3Prefix\",\n",
    "                    \"S3Uri\": \"s3://{}/{}/train/\".format(bucket, prefix),\n",
    "                    \"S3DataDistributionType\": \"ShardedByS3Key\"\n",
    "                }\n",
    "            },\n",
    "            \"CompressionType\": \"None\",\n",
    "            \"RecordWrapperType\": \"None\"\n",
    "        },\n",
    "        {\n",
    "            \"ChannelName\": \"validation\",\n",
    "            \"DataSource\": {\n",
    "                \"S3DataSource\": {\n",
    "                    \"S3DataType\": \"S3Prefix\",\n",
    "                    \"S3Uri\": \"s3://{}/{}/validation/\".format(bucket, prefix),\n",
    "                    \"S3DataDistributionType\": \"FullyReplicated\"\n",
    "                }\n",
    "            },\n",
    "            \"CompressionType\": \"None\",\n",
    "            \"RecordWrapperType\": \"None\"\n",
    "        }\n",
    "\n",
    "    ],\n",
    "    \"OutputDataConfig\": {\n",
    "        \"S3OutputPath\": \"s3://{}/{}/\".format(bucket, prefix)\n",
    "    },\n",
    "    \"HyperParameters\": {\n",
    "        \"feature_dim\": \"30\",\n",
    "        \"mini_batch_size\": \"100\",\n",
    "        \"predictor_type\": \"regressor\",\n",
    "        \"epochs\": \"10\",\n",
    "        \"num_models\": \"32\",\n",
    "        \"loss\": \"absolute_loss\"\n",
    "    },\n",
    "    \"StoppingCondition\": {\n",
    "        \"MaxRuntimeInSeconds\": 60 * 60\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's kick off our training job in SageMaker's distributed, managed training, using the parameters we just created.  Because training is managed, we don't have to wait for our job to finish to continue, but for this case, let's use boto3's 'training_job_completed_or_stopped' waiter so we can ensure that the job has been started."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%time\n",
    "\n",
    "region = boto3.Session().region_name\n",
    "sm = boto3.client('sagemaker')\n",
    "\n",
    "sm.create_training_job(**linear_training_params)\n",
    "\n",
    "status = sm.describe_training_job(TrainingJobName=linear_job)['TrainingJobStatus']\n",
    "print(status)\n",
    "sm.get_waiter('training_job_completed_or_stopped').wait(TrainingJobName=linear_job)\n",
    "if status == 'Failed':\n",
    "    message = sm.describe_training_job(TrainingJobName=linear_job)['FailureReason']\n",
    "    print('Training failed with the following error: {}'.format(message))\n",
    "    raise Exception('Training job failed')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "nbpresent": {
     "id": "2adcc348-9ab5-4a8a-8139-d0ecd740208a"
    }
   },
   "source": [
    "---\n",
    "## Host\n",
    "\n",
    "Now that we've trained the linear algorithm on our data, let's setup a model which can later be hosted.  We will:\n",
    "1. Point to the scoring container\n",
    "1. Point to the model.tar.gz that came from training\n",
    "1. Create the hosting model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "nbpresent": {
     "id": "c88fb868-01d2-4991-8953-28814c022bdc"
    }
   },
   "outputs": [],
   "source": [
    "linear_hosting_container = {\n",
    "    'Image': container,\n",
    "    'ModelDataUrl': sm.describe_training_job(TrainingJobName=linear_job)['ModelArtifacts']['S3ModelArtifacts']\n",
    "}\n",
    "\n",
    "create_model_response = sm.create_model(\n",
    "    ModelName=linear_job,\n",
    "    ExecutionRoleArn=role,\n",
    "    PrimaryContainer=linear_hosting_container)\n",
    "\n",
    "print(create_model_response['ModelArn'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once we've setup a model, we can configure what our hosting endpoints should be.  Here we specify:\n",
    "1. EC2 instance type to use for hosting\n",
    "1. Initial number of instances\n",
    "1. Our hosting model name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "linear_endpoint_config = 'DEMO-linear-endpoint-config-' + time.strftime(\"%Y-%m-%d-%H-%M-%S\", time.gmtime())\n",
    "print(linear_endpoint_config)\n",
    "create_endpoint_config_response = sm.create_endpoint_config(\n",
    "    EndpointConfigName=linear_endpoint_config,\n",
    "    ProductionVariants=[{\n",
    "        'InstanceType': 'ml.m4.xlarge',\n",
    "        'InitialInstanceCount': 1,\n",
    "        'ModelName': linear_job,\n",
    "        'VariantName': 'AllTraffic'}])\n",
    "\n",
    "print(\"Endpoint Config Arn: \" + create_endpoint_config_response['EndpointConfigArn'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that we've specified how our endpoint should be configured, we can create them.  This can be done in the background, but for now let's run a loop that updates us on the status of the endpoints so that we know when they are ready for use."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%%time\n",
    "\n",
    "linear_endpoint = 'DEMO-linear-endpoint-' + time.strftime(\"%Y%m%d%H%M\", time.gmtime())\n",
    "print(linear_endpoint)\n",
    "create_endpoint_response = sm.create_endpoint(\n",
    "    EndpointName=linear_endpoint,\n",
    "    EndpointConfigName=linear_endpoint_config)\n",
    "print(create_endpoint_response['EndpointArn'])\n",
    "\n",
    "resp = sm.describe_endpoint(EndpointName=linear_endpoint)\n",
    "status = resp['EndpointStatus']\n",
    "print(\"Status: \" + status)\n",
    "\n",
    "sm.get_waiter('endpoint_in_service').wait(EndpointName=linear_endpoint)\n",
    "\n",
    "resp = sm.describe_endpoint(EndpointName=linear_endpoint)\n",
    "status = resp['EndpointStatus']\n",
    "print(\"Arn: \" + resp['EndpointArn'])\n",
    "print(\"Status: \" + status)\n",
    "\n",
    "if status != 'InService':\n",
    "    raise Exception('Endpoint creation did not succeed')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Predict\n",
    "### Predict on Test Data\n",
    "\n",
    "Now that we have our hosted endpoint, we can generate statistical predictions from it.  Let's predict on our test dataset to understand how accurate our model is.\n",
    "\n",
    "There are many metrics to measure classification accuracy.  Common examples include include:\n",
    "- Precision\n",
    "- Recall\n",
    "- F1 measure\n",
    "- Area under the ROC curve - AUC\n",
    "- Total Classification Accuracy \n",
    "- Mean Absolute Error\n",
    "\n",
    "For our example, we'll keep things simple and use total classification accuracy as our metric of choice. We will also evaluate  Mean Absolute  Error (MAE) as the linear-learner has been optimized using this metric, not necessarily because it is a relevant metric from an application point of view. We'll compare the performance of the linear-learner against a naive benchmark prediction which uses majority class observed in the training data set for prediction on the test data.\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Function to convert an array to a csv"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def np2csv(arr):\n",
    "    csv = io.BytesIO()\n",
    "    np.savetxt(csv, arr, delimiter=',', fmt='%g')\n",
    "    return csv.getvalue().decode().rstrip()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we'll invoke the endpoint to get predictions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "runtime= boto3.client('runtime.sagemaker')\n",
    "\n",
    "payload = np2csv(test_X)\n",
    "response = runtime.invoke_endpoint(EndpointName=linear_endpoint,\n",
    "                                   ContentType='text/csv',\n",
    "                                   Body=payload)\n",
    "result = json.loads(response['Body'].read().decode())\n",
    "test_pred = np.array([r['score'] for r in result['predictions']])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's compare linear learner based mean absolute prediction errors from a baseline prediction which uses majority class to predict every instance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "test_mae_linear = np.mean(np.abs(test_y - test_pred))\n",
    "test_mae_baseline = np.mean(np.abs(test_y - np.median(train_y))) ## training median as baseline predictor\n",
    "\n",
    "print(\"Test MAE Baseline :\", round(test_mae_baseline, 3))\n",
    "print(\"Test MAE Linear:\", round(test_mae_linear,3))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's compare predictive accuracy using a classification threshold of 0.5 for the predicted and compare against the majority class prediction from training data set"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "test_pred_class = (test_pred > 0.5)+0;\n",
    "test_pred_baseline = np.repeat(np.median(train_y), len(test_y))\n",
    "\n",
    "prediction_accuracy = np.mean((test_y == test_pred_class))*100\n",
    "baseline_accuracy = np.mean((test_y == test_pred_baseline))*100\n",
    "\n",
    "print(\"Prediction Accuracy:\", round(prediction_accuracy,1), \"%\")\n",
    "print(\"Baseline Accuracy:\", round(baseline_accuracy,1), \"%\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "###### Run the cell below to delete endpoint once you are done."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "sm.delete_endpoint(EndpointName=linear_endpoint)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Extensions\n",
    "\n",
    "- Our linear model does a good job of predicting breast cancer and has an overall accuracy of close to 92%. We can re-run the model with different values of the hyper-parameters, loss functions etc and see if we get improved prediction. Re-running the model with further tweaks to these hyperparameters may provide more accurate out-of-sample predictions.\n",
    "- We also did not do much feature engineering. We can create additional features by considering cross-product/intreaction of multiple features, squaring or raising higher powers of the features to induce non-linear effects, etc. If we expand the features using non-linear terms and interactions, we can then tweak the regulaization parameter to optimize the expanded model and hence generate improved forecasts.\n",
    "- As a further extension, we can use many of non-linear models available through SageMaker such as XGBoost, MXNet etc.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "conda_python3",
   "language": "python",
   "name": "conda_python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  },
  "notice": "Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.  Licensed under the Apache License, Version 2.0 (the License). You may not use this file except in compliance with the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ or in the license file accompanying this file. This file is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License."
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
